# -*- coding: utf-8 -*-
#
# Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community Edition) available.
# Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. All rights reserved.
# Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://opensource.org/licenses/MIT
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
#
"""
DONE：
- 所有 Model 相关的查询都需要查询是否属于：project_id （没有存储外键关系，需要手动处理相应逻辑）
- service 实例化：添加关联 application 的name 到 selector
- 实例化全部的配置文件（不需要前台传参数）
- 生成配置文件时 去掉前端添加的配置项目(Applicaiotn 实例化配置文件中去掉 imageName ／ imageVersion ／ 调度约束类型)
- 环境变量 & 挂载卷 选择 configmap 和 sercret，实例化时处理前端参数
- 负载均衡的实例化,service 中 关联lb 时，1)前端参数校验，2）service 中添加 BCSGROUP label
- 实例化/更新应用: 根据资源的名称查询依赖项 （configmap 和 sercret）
- service 与 Application 关联时，selector 是或的关系

TODO:
- 实例化失败，再次实例化时，应该更新而不是创建数据
- mesos/k8s 创建分开调用底层API
"""
import logging
import json

from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework.exceptions import ValidationError
from rest_framework.renderers import BrowsableAPIRenderer
from django.utils.translation import ugettext as _

from backend.apps.instance.constants import InsState
from backend.accounts import bcs_perm
from backend.apps.configuration.models import CATE_SHOW_NAME
from backend.apps.instance.models import VersionInstance, InstanceConfig
from backend.apps.instance.utils import validate_template_id
from backend.apps.instance.serializers import (VersionInstanceCreateOrUpdateSLZ, PreviewInstanceSLZ,
                                               InstanceNamespaceSLZ)
from backend.apps.instance.utils import (validate_version_id, preview_config_json, handle_all_config,
                                         validate_ns_by_tempalte_id, validate_lb_info_by_version_id,
                                         validate_instance_entity)
from backend.activity_log import client
from backend.apps.datalog.utils import create_data_project, create_and_start_standard_data_flow
from backend.apps.configuration.models import MODULE_DICT
from backend.apps.application.base_views import error_codes
from backend.apps.configuration.tasks import check_instance_status
from backend.utils.renderers import BKAPIRenderer
from backend.apps.configuration.constants import K8sResourceName, MesosResourceName

logger = logging.getLogger(__name__)


def get_ins_by_template_id(template_id):
    # 获取模板集下所有实例化过的 verison
    show_version_name_list = VersionInstance.objects.filter(
        template_id=template_id, is_deleted=False).values('show_version_name', 'id')

    # 将 instance_id 按 show_version_name 分组
    show_name_ins_dict = {}
    for _show in show_version_name_list:
        show_version_name = _show['show_version_name']
        ins_id = _show['id']
        if show_version_name in show_name_ins_dict:
            ins_id_list = show_name_ins_dict[show_version_name]
            ins_id_list.append(ins_id)
            show_name_ins_dict[show_version_name] = ins_id_list
        else:
            show_name_ins_dict[show_version_name] = [ins_id]

    # 根据 show_version_name 查询所有被实例化的资源
    return show_name_ins_dict


def get_res_by_show_name(template_id, show_version_name):
    show_name_ins_dict = get_ins_by_template_id(template_id)
    ins_id_list = show_name_ins_dict.get(show_version_name)

    _ins_config_list = InstanceConfig.objects.filter(
        instance_id__in=ins_id_list, is_deleted=False, is_bcs_success=True
    )

    data = {}
    for _config in _ins_config_list:
        _cate = _config.category
        _name = _config.name
        if _cate in data:
            res_data_list = data[_cate]

            res_name_list = [_r['name'] for _r in res_data_list]
            if _name not in res_name_list:
                res_data_list.append({
                    'name': _name,
                    'id': _name
                })
        else:
            data[_cate] = [{'name': _name, 'id': _name}]
    return data


class TemplateInstView(viewsets.ViewSet):
    renderer_classes = (BKAPIRenderer, BrowsableAPIRenderer)

    def get_exist_showver_name(self, request, project_id, template_id):
        """查询模板集下所有已经实例化过的可见版本名称
        """
        validate_template_id(project_id, template_id)
        # 因为没有回滚操作，需要去掉versioninstance中的is_bcs_success为True
        valid_instance = VersionInstance.objects.filter(template_id=template_id, is_deleted=False)
        show_version_names = valid_instance.values('show_version_name', 'show_version_id')

        data = []
        show_version_name_list = []
        for _show in show_version_names:
            _show_name = _show['show_version_name']
            _show_version_id = _show['show_version_id']
            if _show_name not in show_version_name_list:
                show_version_name_list.append(_show_name)
                # 判断版本下是否还有未删除的资源
                _ins_id_list = valid_instance.filter(show_version_name=_show_name).values_list('id', flat=True)
                is_exists = InstanceConfig.objects.filter(
                    instance_id__in=_ins_id_list, is_deleted=False, is_bcs_success=True
                ).exclude(ins_state=InsState.NO_INS.value).exists()
                if is_exists:
                    data.append({
                        'id': _show_name,
                        'version': _show_name,
                        'template_id': template_id,
                        'show_version_id': _show_version_id
                    })
        # 根据 show_version_id（创建时间） 降序排列
        data = sorted(
            data, key=lambda x: x['show_version_id'], reverse=True)
        return Response({
            "code": 0,
            "message": "OK",
            "data": {
                "results": data
            }
        })

    def get_resource_by_show_name(self, request, project_id, template_id):
        validate_template_id(project_id, template_id)
        show_version_name = request.GET.get('show_version_name')
        data = get_res_by_show_name(template_id, show_version_name)
        # 传给前端的资源类型统一
        new_data = {CATE_SHOW_NAME.get(x, x): data[x] for x in data}
        return Response({
            "code": 0,
            "message": "OK",
            "data": {
                "data": new_data
            }
        })

    def get_exist_version(self, request, project_id, template_id):
        validate_template_id(project_id, template_id)
        show_name_ins_dict = get_ins_by_template_id(template_id)

        # 查询每个版本实例化的数量
        exist_version = {}
        for show_name in show_name_ins_dict:
            ins_id_list = show_name_ins_dict.get(show_name)
            _ins_count = InstanceConfig.objects.filter(
                instance_id__in=ins_id_list, is_deleted=False, is_bcs_success=True
            ).exclude(ins_state=InsState.NO_INS.value).count()
            if _ins_count > 0:
                exist_version[show_name] = _ins_count
        return Response({
            "code": 0,
            "message": "OK",
            "data": {
                "exist_version": exist_version
            }
        })


class VersionInstanceView(viewsets.ViewSet):
    renderer_classes = (BKAPIRenderer, BrowsableAPIRenderer)

    def preview_config(self, request, project_id):
        project_kind = request.project.kind
        serializer = PreviewInstanceSLZ(data=request.data, context={
            'project_kind': project_kind})
        serializer.is_valid(raise_exception=True)
        slz_data = serializer.data

        # 验证 version_id 是否属于该项目
        version_id = slz_data['version_id']
        show_version_id = slz_data['show_version_id']

        template, version_entity = validate_version_id(
            project_id, version_id, is_return_all=True, show_version_id=show_version_id)
        # 验证用户是否有使用权限
        perm = bcs_perm.Templates(request, project_id, template.id, template.name)
        perm.can_use(raise_exception=True)

        template_id = version_entity.template_id
        version = version_entity.version
        tem_instance_entity = version_entity.get_version_instance_resource_ids

        # 验证前端传过了的预览资源是否在该版本的资源
        req_instance_entity = slz_data.get('instance_entity') or {}
        instance_entity = validate_instance_entity(
            req_instance_entity, tem_instance_entity)

        access_token = self.request.user.token.access_token
        username = self.request.user.username

        namespace = slz_data['namespace']
        lb_info = slz_data.get('lb_info', {})
        # 验证关联lb情况下，lb 是否都已经选中
        service_id_list = instance_entity.get('service') or []
        v_res, err_list, err_msg = validate_lb_info_by_version_id(
            access_token, project_id, version_entity, [namespace], lb_info, service_id_list)
        if not v_res:
            return Response({
                "code": 400,
                "message": err_msg,
                "data": err_list
            })
        # 在数据平台创建项目信息
        cc_app_id = request.project.cc_app_id
        english_name = request.project.english_name
        create_data_project(request.user.username, project_id, cc_app_id, english_name)

        # 查询当前命名空间的变量信息
        variable_dict = slz_data.get('variable_info', {}).get(namespace) or {}
        params = {
            "instance_id": "instanceID",
            "version_id": version_id,
            "show_version_id": show_version_id,
            "template_id": template_id,
            "version": version,
            "project_id": project_id,
            "access_token": access_token,
            "username": username,
            "lb_info": lb_info,
            "variable_dict": variable_dict,
            "is_preview": True
        }
        data = preview_config_json(
            namespace, instance_entity, **params)
        return Response({
            "code": 0,
            "message": "OK",
            "data": data
        })

    def get_tmpl_name(self, instance_entity):
        all_tmpl_name_dict = {}
        for category, tmpl_id_list in instance_entity.items():
            # 需要单独处理 Metric, 不需要轮询Metric 的状态
            # 非标准日志采集相关的 configmap\K8sConfigMap 不需要轮询状态
            # secret 相关的也不需要轮询状态
            if category in ['metric', 'configmap', 'K8sConfigMap', 'secret', 'K8sSecret']:
                continue

            if category not in MODULE_DICT:
                raise error_codes.CheckFailed(_("类型只能为{}").format(";".join(MODULE_DICT.keys())))
            tmpl_name = MODULE_DICT[category].objects.filter(id__in=tmpl_id_list).values_list("name", flat=True)
            if category in all_tmpl_name_dict:
                all_tmpl_name_dict[category].extend(list(tmpl_name))
            else:
                all_tmpl_name_dict = {category: list(tmpl_name)}
        return all_tmpl_name_dict

    def post(self, request, project_id):
        """实例化模板
        """
        # 参数验证
        self.project_id = project_id
        version_id = request.data.get('version_id')
        show_version_id = request.data.get('show_version_id')

        template, version_entity = validate_version_id(
            project_id, version_id, is_return_all=True, show_version_id=show_version_id)
        # 验证用户是否有使用权限
        perm = bcs_perm.Templates(request, project_id, template.id, template.name)
        perm.can_use(raise_exception=True)

        self.template_id = version_entity.template_id
        tem_instance_entity = version_entity.get_version_instance_resource_ids

        project_kind = request.project.kind
        self.slz = VersionInstanceCreateOrUpdateSLZ(
            data=request.data, context={'project_kind': project_kind})
        self.slz.is_valid(raise_exception=True)
        slz_data = self.slz.data

        # 验证前端传过了的预览资源是否在该版本的资源
        req_instance_entity = slz_data.get('instance_entity') or {}
        self.instance_entity = validate_instance_entity(
            req_instance_entity, tem_instance_entity)

        namespaces = slz_data['namespaces']
        ns_list = namespaces.split(',') if namespaces else []

        access_token = self.request.user.token.access_token
        username = self.request.user.username

        # 验证关联lb情况下，lb 是否都已经选中
        service_id_list = self.instance_entity.get('service') or []
        v_res, err_list, err_msg = validate_lb_info_by_version_id(
            access_token, project_id, version_entity, ns_list, slz_data.get('lb_info', {}), service_id_list)
        if not v_res:
            return Response({
                "code": 400,
                "message": err_msg,
                "data": err_list
            })

        # 判断 template 下 前台传过来的 namespace 是否已经实例化过
        res, ns_name_list, namespace_dict = validate_ns_by_tempalte_id(
            self.template_id, ns_list, access_token, project_id, req_instance_entity)
        if not res:
            return Response({
                "code": 400,
                "message": _("以下命名空间已经实例化过，不能再实例化\n{}").format("\n".join(ns_name_list)),
                "data": ns_name_list
            })

        # 在数据平台创建项目信息
        cc_app_id = request.project.cc_app_id
        english_name = request.project.english_name
        create_data_project(request.user.username, project_id, cc_app_id, english_name)
        # 创建/启动标准日志采集任务
        create_and_start_standard_data_flow(username, project_id, cc_app_id)

        slz_data['ns_list'] = ns_list
        slz_data['instance_entity'] = self.instance_entity
        slz_data['template_id'] = self.template_id
        slz_data['project_id'] = project_id
        slz_data['version_id'] = version_id
        slz_data['show_version_id'] = show_version_id

        result = handle_all_config(slz_data, access_token, username, project_kind=request.project.kind)
        instance_entity = slz_data.get("instance_entity")
        all_tmpl_name_dict = self.get_tmpl_name(instance_entity)

        # 添加操作记录
        temp_name = version_entity.get_template_name()
        for i in result['success']:
            client.ContextActivityLogClient(
                project_id=project_id,
                user=username,
                resource_type="template",
                resource=temp_name,
                resource_id=self.template_id,
                extra=json.dumps(self.instance_entity),
                description=_("实例化模板集[{}]命名空间[{}]").format(temp_name, i['ns_name'])
            ).log_add(activity_status="succeed")

        failed_ns_name_list = []
        failed_msg = []
        is_show_failed_msg = False
        # 针对createError的触发后台任务轮训
        if result.get('failed'):
            check_instance_status.delay(
                request.user.token.access_token, project_id,
                request.project.get("kind"), all_tmpl_name_dict, result['failed']
            )
        for i in result['failed']:
            if i['res_type']:
                description = _("实例化模板集[{}]命名空间[{}]，在实例化{}时失败，错误消息：{}").format(
                    temp_name, i['ns_name'], i['res_type'], i['err_msg'])
                failed_ns_name_list.append(
                    _("{}(实例化{}时)").format(i['ns_name'], i['res_type']))
            else:
                description = _("实例化模板集[{}]命名空间[{}]失败，错误消息：{}").format(
                    temp_name, i['ns_name'], i['err_msg'])
                failed_ns_name_list.append(i['ns_name'])
                if i.get('show_err_msg'):
                    failed_msg.append(i['err_msg'])
                    is_show_failed_msg = True

            client.ContextActivityLogClient(
                project_id=project_id,
                user=username,
                resource_type="template",
                resource=temp_name,
                resource_id=self.template_id,
                extra=json.dumps(self.instance_entity),
                description=description
            ).log_add(activity_status="failed")

            if is_show_failed_msg:
                msg = '\n'.join(failed_msg)
            else:
                msg = _("以下命名空间实例化失败，\n{}，请联系集群管理员解决").format("\n".join(
                    failed_ns_name_list))
            if failed_ns_name_list:
                return Response({
                    "code": 400,
                    "message": msg,
                    "data": failed_ns_name_list
                })

        return Response({
            "code": 0,
            "message": "OK",
            "data": {
                "version_id": version_id,
                "template_id": self.template_id,
            }
        })


class InstanceNameSpaceView(viewsets.ViewSet):
    renderer_classes = (BKAPIRenderer, BrowsableAPIRenderer)

    def post(self, request, project_id, version_id):
        version_entity = validate_version_id(
            project_id, version_id, is_version_entity_retrun=True)
        template_id = version_entity.template_id

        project_kind = request.project.kind
        self.slz = InstanceNamespaceSLZ(data=request.data, context={
            'project_kind': project_kind})
        self.slz.is_valid(raise_exception=True)
        slz_data = self.slz.data

        if 'instance_entity' not in slz_data:
            raise ValidationError(_("请选择要实例化的模板"))
        instance_entity = slz_data['instance_entity']

        # 根据template_id 查询已经被实例化过的 ns
        exist_instance_id = VersionInstance.objects.filter(
            template_id=template_id, is_deleted=False).values_list('id', flat=True)
        filter_ns = InstanceConfig.objects.filter(
            instance_id__in=exist_instance_id, is_deleted=False, is_bcs_success=True
        ).exclude(ins_state=InsState.NO_INS.value)

        exist_ns = []
        # 查询每类资源已经实例化的ns，求合集，这些已经实例化过的ns不能再被实例化
        for cate in instance_entity:
            cate_data = instance_entity[cate]
            cate_name_list = [i.get('name')
                              for i in cate_data if i.get('name')]
            cate_ns = filter_ns.filter(category=cate, name__in=cate_name_list)
            exist_ns.extend(list(cate_ns))

        ns_resources = {}
        for inst_config in exist_ns:
            ns_id = int(inst_config.namespace)

            # HPA只通过模板集管理，可以重试实例化(apply操作)
            if inst_config.category in [K8sResourceName.K8sHPA.value, MesosResourceName.hpa.value]:
                continue

            if ns_id not in ns_resources:
                ns_resources[ns_id] = [inst_config.category, ]
            else:
                ns_resources[ns_id].append(inst_config.category)

        for ns_id, resources in ns_resources.items():
            ns_resources[ns_id] = list(set(resources))

        return Response({
            "code": 0,
            "message": "OK",
            "data": {
                "ns_resources": ns_resources
            }
        })
